﻿using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Infrastructure.Models;
using Grand.Web.Common.DataSource;
using Grand.Web.Common.Extensions;
using Grand.Web.Common.Filters;
using Grand.Web.Common.Page;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Grand.Web.Common.Controllers;

/// <summary>
///     Base controller
/// </summary>
[PasswordExpired]
[CustomerActivity]
public abstract class BaseController : Controller
{
    #region Notifications

    /// <summary>
    ///     Display success notification
    /// </summary>
    /// <param name="message">Message</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    protected void Success(string message, bool persistNextRequest = true)
    {
        Notification(NotifyType.Success, message, persistNextRequest);
    }

    /// <summary>
    ///     Display warning notification
    /// </summary>
    /// <param name="message">Message</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    protected void Warning(string message, bool persistNextRequest = true)
    {
        Notification(NotifyType.Warning, message, persistNextRequest);
    }

    /// <summary>
    ///     Display error notification
    /// </summary>
    /// <param name="message">Message</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    protected void Error(string message, bool persistNextRequest = true)
    {
        Notification(NotifyType.Error, message, persistNextRequest);
    }

    /// <summary>
    ///     Display error notification
    /// </summary>
    /// <param name="modelState">Model state</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    protected void Error(ModelStateDictionary modelState, bool persistNextRequest = true)
    {
        var modelErrors = new List<string>();
        foreach (var value in modelState.Values)
            modelErrors.AddRange(value.Errors.Select(modelError => modelError.ErrorMessage));
        Notification(NotifyType.Error, string.Join(',', modelErrors), persistNextRequest);
    }

    /// <summary>
    ///     Display error notification
    /// </summary>
    /// <param name="exception">Exception</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    /// <param name="logException">A value indicating whether exception should be logged</param>
    protected void Error(Exception exception, bool persistNextRequest = true, bool logException = true)
    {
        if (logException)
            LogException(exception);

        Notification(NotifyType.Error, exception.Message, persistNextRequest);
    }

    /// <summary>
    ///     Log exception
    /// </summary>
    /// <param name="exception">Exception</param>
    private void LogException(Exception exception)
    {
        var logger = HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("BaseController");
        logger.LogError(exception, exception.Message);
    }

    /// <summary>
    ///     Display notification
    /// </summary>
    /// <param name="type">Notification type</param>
    /// <param name="message">Message</param>
    /// <param name="persistNextRequest">A value indicating whether a message should be persisted for the next request</param>
    protected void Notification(NotifyType type, string message, bool persistNextRequest)
    {
        var dataKey = $"grand.notifications.{type}";

        if (persistNextRequest)
        {
            if (TempData[dataKey] == null || TempData[dataKey] is not List<string>)
                TempData[dataKey] = new List<string>();
            ((List<string>)TempData[dataKey]).Add(message);
        }
        else
        {
            //1. Compare with null (first usage)
            //2. For some unknown reasons sometimes List<string> is converted to string[]. And it throws exceptions. That's why we reset it
            if (ViewData[dataKey] == null || ViewData[dataKey] is not List<string>)
                ViewData[dataKey] = new List<string>();
            ((List<string>)ViewData[dataKey]).Add(message);
        }
    }

    /// <summary>
    ///     Error's json data for kendo grid
    /// </summary>
    /// <param name="errorMessage">Error message</param>
    /// <returns>Error's json data</returns>
    protected JsonResult ErrorForKendoGridJson(string errorMessage)
    {
        var gridModel = new DataSourceResult {
            Errors = errorMessage
        };

        return Json(gridModel);
    }

    /// <summary>
    ///     Error's json data for kendo grid
    /// </summary>
    /// <param name="modelState">Model state</param>
    /// <returns>Error's json data</returns>
    protected JsonResult ErrorForKendoGridJson(ModelStateDictionary modelState)
    {
        var gridModel = new DataSourceResult {
            Errors = modelState.SerializeErrors()
        };
        return Json(gridModel);
    }

    /// <summary>
    ///     Display "Edit" (manage) link (in public store)
    /// </summary>
    /// <param name="editPageUrl">Edit page URL</param>
    protected virtual void DisplayEditLink(string editPageUrl)
    {
        HttpContext.RequestServices.GetRequiredService<IPageHeadBuilder>().EditPageUrl = editPageUrl;
    }

    #endregion

    #region Localization

    /// <summary>
    ///     Add locales for localizable entities
    /// </summary>
    /// <typeparam name="TLocalizedModelLocal">Localizable model</typeparam>
    /// <param name="languageService">Language service</param>
    /// <param name="locales">Locales</param>
    protected virtual async Task AddLocales<TLocalizedModelLocal>(ILanguageService languageService,
        IList<TLocalizedModelLocal> locales) where TLocalizedModelLocal : ILocalizedModelLocal
    {
        await AddLocales(languageService, locales, null);
    }

    /// <summary>
    ///     Add locales for localizable entities
    /// </summary>
    /// <typeparam name="TLocalizedModelLocal">Localizable model</typeparam>
    /// <param name="languageService">Language service</param>
    /// <param name="locales">Locales</param>
    /// <param name="configure">Configure action</param>
    /// https://github.com/smartstore/Smartstore/blob/98494cd31f5a376bf67010264e10c37a657f6ee9/src/Smartstore.Web.Common/Controllers/ManageController.cs
    protected virtual async Task AddLocales<TLocalizedModelLocal>(ILanguageService languageService,
        IList<TLocalizedModelLocal> locales, Action<TLocalizedModelLocal, string> configure)
        where TLocalizedModelLocal : ILocalizedModelLocal
    {
        foreach (var language in await languageService.GetAllLanguages(true))
        {
            var locale = Activator.CreateInstance<TLocalizedModelLocal>();
            locale.LanguageId = language.Id;

            configure?.Invoke(locale, locale.LanguageId);

            locales.Add(locale);
        }
    }

    #endregion

    #region Security

    /// <summary>
    ///     Access denied view
    /// </summary>
    /// <returns>Access denied view</returns>
    protected IActionResult AccessDeniedView()
    {
        return RedirectToAction("AccessDenied", "Home");
    }
    #endregion
}